package com.hontech.desktoppastacooker.conn

import com.hontech.desktoppastacooker.codec.decodeUint8
import com.hontech.desktoppastacooker.conn.frame.Frame
import com.hontech.desktoppastacooker.conn.frame.NotifyFrame
import com.hontech.desktoppastacooker.conn.frame.ResFrame
import com.hontech.desktoppastacooker.conn.frame.ResultFrame
import com.hontech.desktoppastacooker.conn.serial.ByteView
import com.hontech.desktoppastacooker.device.Device
import com.hontech.desktoppastacooker.exception.AckTimeoutException
import com.hontech.desktoppastacooker.exception.ResultTimeoutException
import com.hontech.desktoppastacooker.utils.*
import kotlinx.coroutines.CompletableDeferred

class SerialPortReader(private val port: SerialPort) : Thread("serial-port-reader") {

    private val exitPromise = CompletableDeferred<Unit>()
    private val ackSync = WaitNotify()
    private val router = Router()
    private val resultSync = SyncValue<ResultFrame>()

    init {
        start()
    }

    suspend fun close() {
        port.notifyClose()
        exitPromise.await()
        port.close()
    }

    fun queryBegin() {
        resultSync.reset()
    }

    fun resultAwait(): ResultFrame {
        val result = resultSync.await(500)
        return result ?: throw ResultTimeoutException("无法收到返回数据")
    }

    fun ackReset() {
        ackSync.reset()
    }

    fun ackAwait() {
        val ret = ackSync.await(500)
        if (!ret) {
            throw AckTimeoutException("没有收到ACK")
        }
    }

    fun reqBegin(seq: Int, cmd: Int): CompletableDeferred<ResFrame> {
        ackSync.reset()
        return router.set(seq, cmd)
    }

    override fun run() {

        while (true) {
            try {
                runOnce()
            } catch (e: IllegalStateException) {
                e.printStackTrace()
            } catch (e: Exception) {
                e.printStackTrace()
                break
            }
        }
        log("serial port reader exit")
        exitPromise.complete(Unit)
    }

    /**
     * 0xE1 0x1E
     * len
     * type
     * ...
     * sum
     * 0xEF
     */
    private fun runOnce() {
        val size = port.sync()
        if (size < 7) {
            throw IllegalStateException("size < 7 => $size")
        }

        val buf = ByteArray(size - 3)
        port.readBytes(buf)

        val s = buf.xorSum(0, size - 5)

        val sum = decodeUint8(buf, buf.size - 2)
        val end = decodeUint8(buf, buf.size - 1)
        if (s != sum) {
            throw IllegalStateException("sum fail")
        }
        if (end != Proto.END) {
            throw IllegalStateException("end fail")
        }
        dispatch(buf)
    }

    private fun dispatch(buf: ByteArray) {
       // log("recv:${buf.toHex()}")

        when (buf[0].toUint8()) {

            Frame.TYPE_ACK -> {
                ackSync.signal()
            }

            Frame.TYPE_NOTIFY -> {
                val cmd = buf[1].toUint8()
                handleNotify(NotifyFrame(cmd, buf))
            }

            Frame.TYPE_RES -> {
                val seq = buf[1].toUint8()
                val cmd = buf[2].toUint8()
                port.writeAck(seq)
                handleRes(ResFrame(seq, cmd, buf))
            }

            Frame.TYPE_RESULT -> {
                val cmd = buf[1].toUint8()
                val frame = ResultFrame(cmd, buf)
                resultSync.signal(frame)
            }
        }
    }

    private fun handleRes(frame: ResFrame) {
        router.notify(frame)
    }

    private fun handleNotify(frame: NotifyFrame) {

        when (frame.cmd) {

            Proto.NOTIFY_LOG -> {
                val bw = ByteView()
                frame.parse(arrayOf(bw))
                log("log:$bw")
            }

            Proto.NOTIFY_STATUS -> {
                Device.recvStatus(frame)
            }

            Proto.NOTIFY_RF -> {
                Device.recvRfid(frame)
            }
        }
    }

}











